﻿<!DOCTYPE html>
<html>
<head>
<!--
	Copyright (c) 2018 Jean-Marc VIGLINO,
	released under CeCILL-B (french BSD like) licence: http://www.cecill.info/
-->
	<title>ol-ext: Dijkstra short path</title>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

	<meta name="description" content="Add a routing control to your map." />
	<meta name="keywords" content="ol3, control, search, routing" />

	<!-- FontAwesome -->
	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

	<!-- jQuery -->
	<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.0.min.js"></script>
	<!-- IE 9 bug on ajax tranport  -->
	<!--[if lte IE 9]>
	<script type='text/javascript' src='//cdnjs.cloudflare.com/ajax/libs/jquery-ajaxtransport-xdomainrequest/1.0.3/jquery.xdomainrequest.min.js'></script>
	<![endif]-->

	<!-- Openlayers -->
  <link rel="stylesheet" href="https://openlayers.org/en/latest/css/ol.css" />
	<script type="text/javascript" src="https://openlayers.org/en/latest/build/ol.js"></script>
  <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL,Object.assign"></script>
	
	<!-- ol-ext -->
  <link rel="stylesheet" href="../../dist/ol-ext.css" />
	<script type="text/javascript" src="../../dist/ol-ext.js"></script>

	<script type="text/javascript" src="https://cdn.rawgit.com/Viglino/Geoportail-KISS/gh-pages/apikey.js"></script>
	
	<link rel="stylesheet" href="../style.css"/>
	<style>
    .loading {
      position: fixed;
      color: #666;
      top: 30%;
      left: 50%;
      background: #fff;
      font-size: 2em;
      padding: 1em;
      z-index: 10;
      box-shadow: 1px 1px 10px 2px rgba(0,0,0,.5);
      border-radius: 3px;
      transform: translateX(-50%);
    }
    .loading .fa {
      vertical-align: middle;
      font-size: 1.5em;
    }
    .options ul {
      margin: 0;
      padding: 0;
    }
    .speed label {
      display: inline-block;
      width: 5.5em;
      text-align: right;
    }
    input[type="number"] {
      width: 3em;
    }
	</style>
</head>
<body >
  <a href="https://github.com/Viglino/ol-ext" class="icss-github-corner"><i></i></a>

	<a href="../../index.html">
		<h1>ol-ext: Dijkstra short path</h1>
  </a>
  <div class=loading>
    <i class="fa fa fa-spinner fa-pulse"></i> Loading data...
  </div>

	<div class="info">
    <p>
      The <i>ol/graph/Dijkstra</i> use the
      <a href="https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm">Dijkstra's algorithm</a> 
      for finding the shortest paths between nodes in a ol/source/Vector (with LineString geometry features).
      <br/>
      It uses a A* optimisation.
    </p>
    <p>
      You can overwrite methods to customize the result:
      <ul>
        <li>
          <i>getLength()</i> method enable you to specify an other distance calculation.
        </li>
        <li>
            <i>weight()</i> method enable weighting on the graph, for example calculate a speed factor on the edges.
          </li>
        <li>
          <i>direction()</i> method can defined the direction of the edges (-1, 0, 1 or 2).
        </li>
      </ul>
    </p>

    <p>
      <img src="http://professionnels.ign.fr/sites/default/files/Logo-licence-ouverte.png" style="float:left; height:3em; margin-right:.5em;"/>
      <img src="https://static.data.gouv.fr/avatars/1b/e4985396724faf9f6e1122baa7b65c.gif" style="float:left; height:3em; margin-right:.5em; background:#fff;"/>
      The graph used in this example is <a href="http://professionnels.ign.fr/route120">ROUTE 120&reg;</a>, 
      an opensource (Open licence) database from IGN-France.
    </p>
	</div>

	<!-- DIV pour la carte -->
	<div id="map" style="width:600px; height:400px;"></div>

	<div class="options">
    <h2>Options</h2>
    <ul>
      <li>
        While calculating:<br/>
        <label><input name="view" type="radio" checked onclick="nodes.setVisible(true)"> show nodes</label>
        <br/>
        <label><input id="path" name="view" type="radio" onclick="nodes.setVisible(false)"> show path</label>
      </li>
      <li>
        <hr/>
        <label><input id="speed" type="checkbox" onchange="calcSpeed()"> Use speed:</label>
        <div class="speed">
          <label>highway:</label> <input id="A" type="number" value=120 onchange="calcSpeed()" />
          <br/>
          <label>main:</label> <input id="P" type="number" value=80 onchange="calcSpeed()" />
          <br/>
          <label>regional:</label> <input id="R" type="number" value=70 onchange="calcSpeed()" />
          <br/>
          <label>local:</label> <input id="L" type="number" value=60 onchange="calcSpeed()" />
        </div>
      </li>
    </ul>
  </div>
  <p>Click points on the map!</p>
  <p id="warning" style="background:#f80; color:#fff; padding:.5em; display:none; float:left;">
    Ugh! it's a bit long!
    <br/>
    Try to go on searching...
  </p>
  <p id="notfound" style="background:#f80; color:#fff; padding:.5em; display:none; float:left;">
    Sorry, there is no route to go there!
  </p>
  <p id="notfound0" style="background:#f80; color:#fff; padding:.5em; display:none; float:left;">
    You're allready there!
  </p>
  <p id="result" style="background:#0b0; color:#fff; padding:.5em; display:none; float:left;">
    Found it!
    <br/>
    Distance: <span></span>.
  </p>
	 
  <script type="text/javascript">
  // Calculate the speed factor
  var speed = { A:1, P:1, R:1, L:1};
  function calcSpeed() {
    if ($("#speed").prop('checked')) {
      speed.A = 1 / Math.max(Number($(".speed #A").val()),1);
      speed.P = 1 / Math.max(Number($(".speed #P").val()),1);
      speed.R = 1 / Math.max(Number($(".speed #R").val()),1);
      speed.L = 1 / Math.max(Number($(".speed #L").val()),1);
    } else {
      speed = { A:1, P:1, R:1, L:1};
    }
  }
  calcSpeed();

	// Layers
	var layers = [ new ol.layer.Tile({ source: new ol.source.Stamen({ layer: 'watercolor' }) }) ];

	// The map
	var map = new ol.Map ({
    target: 'map',
    view: new ol.View ({
      zoom: 6,
      center: [166326, 5992663]
    }),
    interactions: ol.interaction.defaults({ altShiftDragRotate:false, pinchRotate:false }),
    layers: layers
  });

  // The vector graph
  var graph = new ol.source.Vector({
    url: '../data/ROUTE120.geojson',
    format: new ol.format.GeoJSON()
  });
  listenerKey = graph.on('change', function() {
    if (graph.getState() == 'ready') {
      $('.loading').hide();
      ol.Observable.unByKey(listenerKey);
    }
  });
	var vector = new ol.layer.Vector({
    title: 'Graph',
		source: graph
	});
  map.addLayer(vector);

  // A layer to draw the result
  var result = new ol.source.Vector();
  map.addLayer ( new ol.layer.Vector({
    source: result,
    style: new ol.style.Style({ 
      stroke: new ol.style.Stroke({ 
        width: 2,
        color: "#f00"
      }) 
    })
  }));
  
  // Dijkstra
  var dijkstra = new ol.graph.Dijskra({
		source: graph
  });
  // Start processing
  dijkstra.on('start', function(e) {
    $('#warning').hide();
    $("#notfound").hide();
    $("#notfound0").hide();
    $("#result").hide();
    result.clear();
  });
  // Finish > show the route
  dijkstra.on('finish', function(e) {
    $('#warning').hide();
    result.clear();
    console.log(e);
    if (!e.route.length) {
      if (e.wDistance===-1) $("#notfound0").show();
      else $("#notfound").show();
      $("#result").hide();
    } else {
      $("#result").show();
      var t = (e.distance/1000).toFixed(2) + 'km';
      // Weighted distance
      if ($("#speed").prop('checked')) {
        var h = e.wDistance/1000;
        var mn = Math.round((e.wDistance%1000)/1000*60);
        if (mn < 10) mn = '0'+mn;
        t += '<br/>' + h.toFixed(0) + 'h ' + mn + 'mn';
      }
      $("#result span").html(t);
    }
    result.addFeatures(e.route);
    start = end;
    popStart.show(start);
    popEnd.hide();
  });
  // Paused > resume
  dijkstra.on('pause', function(e) {
    if (e.overflow) {
      $('#warning').show();
      dijkstra.resume();
    } else {
      // User pause
    }
  });
  // Calculating > show the current "best way"
  dijkstra.on('calculating', function(e) {
    if ($('#path').prop('checked')) {
      var route = dijkstra.getBestWay();
      result.clear();
      result.addFeatures(route);
    }
  });

  // Get the weight of an edge
  dijkstra.weight = function(feature) {
    var type = feature ? feature.get('type') : 'A';
    if (!speed[type]) console.error(type)
    return speed[type] || speed.L;
  };
  // Get direction of the edge
  dijkstra.direction = function(feature) {
    return feature.get('dir');
  }
  // Get the real length of the geom
  dijkstra.getLength = function(geom) {
    if (geom.getGeometry) {
      //? return geom.get('km')*1000;
      geom = geom.getGeometry();
    }
    return ol.sphere.getLength(geom)
  }

  // Display nodes in a layer
	var nodes = new ol.layer.Vector({
    title: 'Nodes',
    source: dijkstra.getNodeSource(),
    style: new ol.style.Style({
      image: new ol.style.Circle({
        radius: 5,
        fill: new ol.style.Fill({ color: [255,0,0,.1] })
      })
    })
	});
  map.addLayer(nodes);

  // Start / end Placemark
  var popStart = new ol.Overlay.Placemark({ popupClass: 'flagv', color: '#080' });
  map.addOverlay(popStart);
  var popEnd = new ol.Overlay.Placemark({ popupClass: 'flag finish', color: '#000' });
  map.addOverlay(popEnd);

  // Manage start / end on click
  var start, end;
  map.on('click', function(e) {
    if (!start) {
      start = e.coordinate;
      popStart.show(start);
    } else {
      var se = dijkstra.path(start, e.coordinate);
      if (se) {
        start = se[0];
        end = se[1];
        popEnd.show(end);
      }
    }
  });

	</script>
	
</body>
</html>